﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections;
using ValidationFramework.Reflection;

namespace ValidationFramework.Configuration.Reflection
{
	public class RuleAttributeScanner
	{

		// --- Fields

		TypePolicyRepository _typePolicyRepository;
		MethodPolicyRepository _methodPolicyRepository;
		ReflectionCache _reflectionCache;
        BindingFlags _bindingFlags = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public;
		public bool ScanPropertyAncestry { get; set; }
		public bool ScanParameterAncestry { get; set; }

		// --- Constructors
		/// <summary>
        /// Initializes a new instance of the RuleAttributeScanner class.
        /// </summary>
        /// <param name="typePolicyCache"></param>
        /// <param name="methodPolicyCache"></param>
        public RuleAttributeScanner(TypePolicyRepository typePolicyCache, MethodPolicyRepository methodPolicyCache, ReflectionCache reflectionCache)
        {
    		_typePolicyRepository = typePolicyCache;
    		_methodPolicyRepository = methodPolicyCache;
			_reflectionCache = reflectionCache;

			ScanPropertyAncestry = true;
			ScanParameterAncestry = true;
        }


		public void AddAssemblyRules(Assembly assembly)
		{
			var allTypes = assembly.GetTypes();
			//var baseTypes = allTypes.Select(type => type.BaseType);
			//var bottomTypes = allTypes.Except(baseTypes);

			foreach (var type in allTypes)
			{
				AddRulesForType(assembly, type);
			}
		}

        /// <summary>
		/// This will search the assembly for rule attributes for the given type and add those rules to the validation framework. If
		/// the framework has attribute inheritance enabled it will also look back across the target type's ancestry for rules.
		/// </summary>
		/// <param name="assembly"></param>
		/// <param name="targetType"></param>
		private void AddRulesForType(Assembly assembly, Type targetType)
		{
			foreach (var property in targetType.GetProperties(_bindingFlags))
			{
				ScanProperty(property);
			} 
		}


		internal void ScanProperty(PropertyInfo property)
		{
			if (property.ReflectedType.IsInterface)
				return; // ignore properties on interfaces

			// Get this property's attributes
			var attributes = property.GetCustomAttributes((typeof(RuleAttribute)), ScanPropertyAncestry);

			if (attributes.Length > 0)
			{
				// Create fast propertyInfo to help attribute create rule
				var key = property.ToPropertyKey();
				var cachedProperty = _reflectionCache.GetCachedProperty(key);

				// Create and add rules to the property's policy
				foreach (IRuleAttribute attribute in attributes)
				{
					// TODO: This line implies that inorder to create a rule we need to know what property its being applied to
					// does this make the propertyKey param redundant? Does this make other things redundant?
					_typePolicyRepository.AddPropertyRule(key, attribute.CreateRule(cachedProperty), attribute.Scope);
				}
			}
		}
	}
}
